#include <libusb.h>
#include <linux/hid.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define DEV_VID 0x0471
#define DEV_PID 0x082f
#define HID_IF  0x3
#define HID_AS  0x0
#define HID_EP  0x83

#define MESSAGE_TYPE_INIT	0xc1

#define STATUS_ONLINE		0x00
#define STATUS_OFFLINE		0x01
#define STATUS_NOT_AVAILABLE	0x02
#define STATUS_AWAY		0x03
#define STATUS_INVISIBLE	0x04
#define STATUS_DND		0x05

void dev_read(libusb_device_handle *device, int timeout);

void hw_read(struct libusb_transfer *xfer) {
	if (xfer->status == LIBUSB_TRANSFER_TIMED_OUT || xfer->actual_length == 0)
		goto end;

	printf("R: ", xfer->status);
	int i;
	for(i = 1; i < xfer->actual_length; ++i) printf("%02x ", xfer->buffer[i]);
	printf("\n");

	switch(xfer->buffer[1]) {
		case MESSAGE_TYPE_INIT:
			dev_read(xfer->dev_handle, 100);
			break;
	}

end:
	dev_read(xfer->dev_handle, 20000);
	/* free the data */
	free(xfer->buffer);
	libusb_free_transfer(xfer);
}

void hw_write(struct libusb_transfer *xfer) {
	/* free the data */
	free(xfer->buffer);
	libusb_free_transfer(xfer);
}

void dev_read(libusb_device_handle *device, int timeout) {
	/* setup the interrupt read */
	struct libusb_transfer *xfer = libusb_alloc_transfer(0);
	libusb_fill_interrupt_transfer(
		xfer,
		device,
		HID_EP,
		malloc(8),
		8,
		hw_read,
		NULL,
		timeout
	);

	/* submit the xfer */
	libusb_submit_transfer(xfer);
}

char dev_write(libusb_device_handle *device, const void *data, const unsigned int length) {
	unsigned int i;
	for(i = 0; i < length; i += 7) {
		/* setup the buffer */
		unsigned char *buffer = (unsigned char*)malloc(LIBUSB_CONTROL_SETUP_SIZE + 9);
		libusb_fill_control_setup(
			buffer,
			LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
			HID_REQ_SET_REPORT,
			0x304,
			HID_IF,
			9
		);

		buffer[LIBUSB_CONTROL_SETUP_SIZE] = 0x04;
		void *b = buffer + LIBUSB_CONTROL_SETUP_SIZE + 1;
		int len = i + 7 > length ? length - i : 7;
		buffer[16] = 0x68;
		memset(b, 0, 7);
		memcpy(b, data + i, len);

		/* seutp the xfer */
		struct libusb_transfer *xfer = libusb_alloc_transfer(0);
		libusb_fill_control_transfer(
			xfer,
			device,
			buffer,
			hw_write,
			NULL,
			100
		);

		printf("W: ", xfer->status);
		int i;
		for(i = 0; i < len; ++i) printf("%02x ", xfer->buffer[i+9]);
		printf("\n");

		/* submit the xfer */
		libusb_submit_transfer(xfer);
	}
}

char hw_init(libusb_device_handle *device) {
	if (!device)
		return 0;

	if (libusb_kernel_driver_active(device, HID_IF) == 1 && libusb_detach_kernel_driver(device, HID_IF) != 0)
		return 0;

	if (libusb_claim_interface(device, HID_IF) != 0) return 0;
	//if (libusb_set_interface_alt_setting(device, HID_IF, HID_AS) != 0) {
	//	libusb_release_interface(device, HID_IF);
	//	return 0;
	//}

	//if (libusb_clear_halt(device, HID_EP) != 0)
	//	return 0;

	return 1;
}

void hw_deinit(libusb_device_handle *device) {
	libusb_release_interface(device, 3);
}

#if 0
/* process pending messages, and poll every second */
void *t_poll(void *ptr) {
	libusb_device_handle *device = ptr;
	const uint8_t buffer[9] = {0x04, 0x82, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68};
	int count = 0;
	while(1) {
		hw_setup_read(device, 100);
		if (++count == 10) {
			hw_send_ctl(device, 0, buffer);
			count = 0;
		}
		usleep(100000);
	}
}

void do_call(libusb_device_handle *device) {
	const uint8_t buffer[9] = {0x04, 0x84, 0x23, 0x00, 0x01, 0x80, 0x00, 0x00, 0x68};
	hw_send_ctl(device, 1, buffer);
}
#endif

typedef struct {
	uint8_t type;
	uint8_t unknown1[7];
	uint8_t time_h;
	uint8_t time_m;
	uint8_t unknown2;
	uint8_t status;
	uint8_t messages;
	uint8_t unknown3;
} voip321_init_t;

int main(int argc, char *argv[]) {
	int			r;
	libusb_context		*ctx;
	libusb_device_handle	*device;

	if ((r = libusb_init(&ctx)) < 0)
		return r;

	device = libusb_open_device_with_vid_pid(ctx, DEV_VID, DEV_PID);
	if (!hw_init(device)) {
		libusb_close(device);
		goto error;
	}

	dev_read(device, 20000);

	time_t t = time(NULL);
	struct tm *lt = localtime(&t);
	voip321_init_t buffer = {
		.type		= MESSAGE_TYPE_INIT,
		.unknown1	= {0x33, 0x00, 0x43, 0x07, 0xbb, 0x4f, 0x05},
		.time_h		= lt->tm_hour,
		.time_m		= lt->tm_min,
		.unknown2	= 0x00,
		.status		= STATUS_ONLINE,
		.messages	= 0x02,
		.unknown3	= 0x00
	};
	dev_write(device, &buffer, sizeof(buffer));

	while(1) {
		libusb_handle_events(ctx);
	}

	hw_deinit(device);
	libusb_exit(ctx);
	return 0;

error:
	libusb_exit(ctx);
	return 1;
}
